package cn.stimd.spring.context.annotation;

import cn.stimd.spring.beans.BeanUtils;
import cn.stimd.spring.beans.factory.config.ConfigurableBeanFactory;
import cn.stimd.spring.beans.factory.support.BeanDefinitionRegistry;
import cn.stimd.spring.context.ConfigurableApplicationContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ConditionEvaluator {
    private final Log logger = LogFactory.getLog(getClass());
    private final ConditionContextImpl context;

    public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) {
        this.context = new ConditionContextImpl(registry, environment, resourceLoader);
    }


    /**
     * 判断当前类是否应该被跳过
     * @param metadata  声明了注解的元数据，可能是方法或类
     * @return 返回true表示跳过不加载，false表示加载
     */
    public boolean shouldSkip(AnnotatedTypeMetadata metadata){
        //1. 没有@Conditional注解，被视为正常的Bean
        if(metadata == null || !metadata.isAnnotated(Conditional.class.getName())){
            return false;
        }

        //2. 解析@Conditional注解，并转换成Condition集合
        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }
        AnnotationAwareOrderComparator.sort(conditions);

        //4. 对所有的Condition进行判定，只要有一个条件不符合，说明不应加载Bean。
        boolean flag = false;
        for (Condition condition : conditions) {
            //进一步判断条件类是否有资格执行
            if (!condition.matches(this.context, metadata)) {
                flag = true;
                break;
            }
        }

        processLog(metadata, flag);     //日志打印，源码无
        return flag;
    }

    private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
        Object values = (attributes != null ? attributes.get("value") : null);
        return (List<String[]>) (values != null ? values : Collections.emptyList());
    }

    private Condition getCondition(String conditionClassName, ClassLoader classloader) {
        Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);
        return (Condition) BeanUtils.instantiateClass(conditionClass);
    }

    private void processLog(AnnotatedTypeMetadata metadata, boolean flag){
        if(logger.isDebugEnabled()){
            String name = "";
            if(metadata instanceof AnnotationMetadata){
                name = ((AnnotationMetadata) metadata).getClassName();
            }else if (metadata instanceof MethodMetadata){
                name = ((MethodMetadata) metadata).getMethodName();
            }
            logger.debug("[Context] [Condition] --> 条件判定, beanName[" + name + "]" + (flag ? "不加载" : "加载"));
        }
    }


    private static class ConditionContextImpl implements ConditionContext{
        private BeanDefinitionRegistry registry;
        private ConfigurableBeanFactory beanFactory;
        private Environment environment;
        private ResourceLoader resourceLoader;

        public ConditionContextImpl(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) {
            this.registry = registry;
            this.environment = environment;
            this.beanFactory = deduceBeanFactory(registry);

            if(resourceLoader != null){
                this.resourceLoader = resourceLoader;
            }else{
                this.resourceLoader = registry instanceof ResourceLoader ? (ResourceLoader) registry : null;
            }
        }


        @Override
        public BeanDefinitionRegistry getRegistry() {
            return this.registry;
        }

        @Override
        public ConfigurableBeanFactory getBeanFactory() {
            return this.beanFactory;
        }

        @Override
        public Environment getEnvironment() {
            return this.environment;
        }

        @Override
        public ResourceLoader getResourceLoader() {
            return this.resourceLoader;
        }

        @Override
        public ClassLoader getClassLoader() {
            if (this.resourceLoader != null) {
                return this.resourceLoader.getClassLoader();
            }
            return null;
        }

        private ConfigurableBeanFactory deduceBeanFactory(BeanDefinitionRegistry source) {
            if (source instanceof ConfigurableBeanFactory) {
                return (ConfigurableBeanFactory) source;
            }
            if (source instanceof ConfigurableApplicationContext) {
                return (((ConfigurableApplicationContext) source).getBeanFactory());
            }
            return null;
        }
    }
}
